//Copyright (c) 2007-2012 Tacke Consulting, Inc

//Permission is hereby granted, free of charge, to any person obtaining a 
//copy of this software and associated documentation files (the "Software"), 
//to deal in the Software without restriction, including without limitation 
//the rights to use, copy, modify, merge, publish, distribute, sublicense, 
//and/or sell copies of the Software, and to permit persons to whom the 
//Software is furnished to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included 
//in all copies or substantial portions of the Software.

//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
//OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
//THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
//FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
//DEALINGS IN THE SOFTWARE. 

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using OpenNETCF.Media.MP3.Frames;

namespace OpenNETCF.Media.MP3
{
    /// <summary>
    /// 
    /// </summary>
    /// <remarks>Developed from spec found at http://www.id3.org/id3v2.4.0-structure
    ///    Overall tag structure:
    /// +-----------------------------+
    /// |      Header (10 bytes)      |
    /// +-----------------------------+
    /// |       Extended Header       |
    /// | (variable length, OPTIONAL) |
    /// +-----------------------------+
    /// |   Frames (variable length)  |
    /// +-----------------------------+
    /// |           Padding           |
    /// | (variable length, OPTIONAL) |
    /// +-----------------------------+
    /// | Footer (10 bytes, OPTIONAL) |
    /// +-----------------------------+
    /// </remarks>
    public class ID3Tag
    {
        private ID3Header m_header;
        private ID3ExtendedHeader m_extendedHeader;
        private FrameCollection m_frames = new FrameCollection();
        private string m_sourceFile;

        private ID3Tag(FileStream stream)
        {
            stream.Position = 0;

            // check for ID3 ID - per the spec:
            /*
                An ID3v2 tag can be detected with the following pattern:
                     $49 44 33 yy yy xx zz zz zz zz
                   Where yy is less than $FF, xx is the 'flags' byte and zz is less than
                   $80.            
            */
            byte[] headerBytes = new byte[ID3Header.HeaderLength];

            stream.Read(headerBytes, 0, headerBytes.Length);
            if ((headerBytes[0] != 'I') || (headerBytes[1] != 'D') || (headerBytes[2] != '3') ||
                (headerBytes[3] == 0xFF) || (headerBytes[4] == 0xFF) ||
                (headerBytes[6] > 0x80) || (headerBytes[7] > 0x80) || (headerBytes[8] > 0x80) || (headerBytes[9] > 0x80))
            {
                // this stream has no tag
                m_header = ID3Header.GetEmptyHeader();
                return;
            }

            m_header = ID3Header.FromBytes(headerBytes);

            // account for extended header if it exists
            if ((m_header.Flags & ID3HeaderFlags.ExtendedHeader) == ID3HeaderFlags.ExtendedHeader)
            {
                m_extendedHeader = ID3ExtendedHeader.FromStream(stream);
            }

            int end = m_header.TagLength + m_header.Length;
            if ((m_header.Flags & ID3HeaderFlags.FooterPresent) == ID3HeaderFlags.FooterPresent)
            {
                end -= ID3Header.HeaderLength;
            }

            // look at frames
            Frames.ID3Frame frame;
            do
            {
                frame = Frames.FrameFactory.GetFrame(stream, end);
                if (frame != null)
                {
                    m_frames.Add(frame);
                }

            } while (frame != null);

            // optional padding

            // optional footer
            if ((m_header.Flags & ID3HeaderFlags.FooterPresent) == ID3HeaderFlags.FooterPresent)
            {
            }
        }

        public string SourceFile
        {
            get { return m_sourceFile; }
        }

        public string EmptyTagString
        {
            get { return m_frames.EmptyTagString; }
            set { m_frames.EmptyTagString = value; }
        }

        public static ID3Tag FromStream(FileStream stream)
        {
            return new ID3Tag(stream);
        }

        public static ID3Tag FromFile(string filePath)
        {
            using (FileStream fs = new FileStream(filePath, FileMode.Open))
            {
                ID3Tag tag = new ID3Tag(fs);
                tag.m_sourceFile = filePath;
                return tag;
            }
        }

        public void WriteToFile()
        {
            if ((m_sourceFile == null) || (m_sourceFile == string.Empty)) return;
            if (!m_frames.HasChanged) return;

            byte[] tagData = m_frames.GetBytes();

            if (tagData.Length > this.Length)
            {
                m_header.TagLength = tagData.Length;
                // tag has grown - we'll need to resize
                // create a temp output file
                string tempName = Path.GetDirectoryName(m_sourceFile);
                tempName = Path.Combine(tempName, "TagRtemp.mp3");
                using (FileStream output = File.Create(tempName))
                {
                    using (FileStream input = File.OpenRead(m_sourceFile))
                    {
                        output.Seek(0, SeekOrigin.Begin);

                        // copy the 10-byte ID3 header
                        output.Write(m_header.GetBytes(), 0, m_header.Length);
                        // write the tag data
                        output.Write(tagData, 0, tagData.Length);

                        byte[] buffer = new byte[4096];
                        input.Seek(this.Length, SeekOrigin.Begin);
                        int read = 0;

                        do
                        {
                            buffer.Initialize();
                            read = input.Read(buffer, 0, buffer.Length);
                            if(read > 0)
                            {
                                output.Write(buffer, 0, read);
                            }
                        } while(read > 0);

                        output.Flush();
                    }                    
                } // using output

                // copy temp to original
                File.Delete(m_sourceFile);
                File.Move(tempName, m_sourceFile);
            }
            else
            {
                m_header.TagLength = tagData.Length;
                // just overwrite the existing tag
                using (FileStream stream = File.OpenWrite(m_sourceFile))
                {
                    stream.Seek(0, SeekOrigin.Begin);
                    stream.Write(m_header.GetBytes(), 0, m_header.Length);
                    stream.Write(tagData, 0, tagData.Length);
                }
            }

        }

        public int Length
        {
            get { return m_header.TagLength; }
        }

        public int Version
        {
            get { return m_header.Version; }
        }

        public string Artist
        {
            get { return m_frames.GetFrameText<ArtistFrame>(); }
            set 
            {
                if (value == null)
                    m_frames.RemoveFrame<ArtistFrame>();
                else
                    m_frames.SetFrameText<ArtistFrame>(value); 
            }
        }

        public string Title
        {
            get { return m_frames.GetFrameText<TitleFrame>(); }
            set
            {
                if (value == null)
                    m_frames.RemoveFrame<TitleFrame>();
                else
                    m_frames.SetFrameText<TitleFrame>(value);
            }
        }

        public string TrackNumber
        {
            get { return m_frames.GetFrameText<TrackFrame>(); }
            set
            {
                if (value == null)
                    m_frames.RemoveFrame<TrackFrame>();
                else
                    m_frames.SetFrameText<TrackFrame>(value);
            }
        }

        public string Album
        {
            get { return m_frames.GetFrameText<AlbumFrame>(); }
            set
            {
                if (value == null)
                    m_frames.RemoveFrame<AlbumFrame>();
                else
                    m_frames.SetFrameText<AlbumFrame>(value);
            }
        }

        public string Publisher
        {
            get { return m_frames.GetFrameText<PublisherFrame>(); }
            set
            {
                if (value == null)
                    m_frames.RemoveFrame<PublisherFrame>();
                else
                    m_frames.SetFrameText<PublisherFrame>(value);
            }
        }

        public string Year
        {
            get { return m_frames.GetFrameText<YearFrame>(); }
            set
            {
                if (value == null)
                    m_frames.RemoveFrame<YearFrame>();
                else
                    m_frames.SetFrameText<YearFrame>(value);
            }
        }

        public Genre Genre
        {
            get { return m_frames.GetFrame<ContentTypeFrame>().Genre; }
        }
    }
}
